﻿using System;
#if WINDOWS_PHONE
using StyleMVVM.Data.Collections;
#else
using System.Collections.Concurrent;
#endif
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using System.Runtime.Serialization;
using System.Text;
using System.Threading.Tasks;
using StyleMVVM.Data;
using StyleMVVM.DependencyInjection.Conditions;
using StyleMVVM.Logging;

namespace StyleMVVM.DependencyInjection.Impl
{
	public sealed class ExportAssemblyConfiguration : IExportAssemblyConfiguration
	{
		private readonly HashSet<Type> registeredTypes;
		private static readonly string supplemental = typeof(ExportAssemblyConfiguration).FullName;
		private readonly List<Type> interfaceTypes = new List<Type>();
		private readonly List<Type> basedOnTypes = new List<Type>();
		private readonly List<Type> excluding = new List<Type>();
		private readonly List<IExportCondition> globalConditions = new List<IExportCondition>();
		private readonly IEnumerable<IExportStrategyFactory> strategyFactories;
		private int assemblyPriority = 0;
		private ExportEnvironment assemblyEnvironment;
		private bool assemblyShared = false;
		private bool assemblySingleton = false;
		private bool assemblyRegisterHandlers = true;
		private bool exportAttributes = true;
		private CoreExportStartOrder? coreExportStart;
		private readonly Assembly assembly;

		public ExportAssemblyConfiguration(object assembly,
													  object registeredTypes,
													  IEnumerable<IExportStrategyFactory> strategyFactories)
		{
			this.assembly = assembly as Assembly;

			if (this.assembly == null && assembly is AssemblyWrapper)
			{
				this.assembly = ((AssemblyWrapper)assembly).Assembly as Assembly;
			}

			if (this.assembly == null)
			{
				throw new ArgumentNullException("assembly", "must be Assembly");
			}

			this.registeredTypes = registeredTypes as HashSet<Type>;
			this.strategyFactories = strategyFactories;
		}

		public IDependencyInjectionContainer Container { get; set; }

		public IExportAssemblyConfiguration ExportInterface(Type interfaceType)
		{
			if (interfaceType == null)
			{
				throw new ArgumentNullException("interfaceType");
			}

			if (!interfaceTypes.Contains(interfaceType))
			{
				interfaceTypes.Add(interfaceType);
			}

			exportAttributes = false;

			return this;
		}

		public IExportAssemblyConfiguration ExportTypesBasedOn(Type baseType)
		{
			if (baseType == null)
			{
				throw new ArgumentNullException("baseType");
			}

			exportAttributes = false;
			basedOnTypes.Add(baseType);

			return this;
		}

		public IExportAssemblyConfiguration Excluding(Type excludeType)
		{
			if (excludeType == null)
			{
				throw new ArgumentNullException("excludeType");
			}

			if (!excluding.Contains(excludeType))
			{
				excluding.Add(excludeType);
			}

			return this;
		}

		public IExportAssemblyConfiguration WithPriority(int priority)
		{
			this.assemblyPriority = priority;

			return this;
		}

		public IExportAssemblyConfiguration InEnvironment(ExportEnvironment environment)
		{
			this.assemblyEnvironment = environment;

			return this;
		}

		public IExportAssemblyConfiguration AndShared()
		{
			assemblyShared = true;

			return this;
		}

		public IExportAssemblyConfiguration AndSharedPermenantly()
		{
			assemblySingleton = true;

			return this;
		}

		public IExportAssemblyConfiguration AndSingleton()
		{
			assemblySingleton = true;

			return this;
		}

		public IExportAssemblyConfiguration ExportAttributedTypes()
		{
			exportAttributes = true;

			return this;
		}

		public IExportAssemblyConfiguration CoreExport(CoreExportStartOrder startOrder)
		{
			coreExportStart = startOrder;

			return this;
		}

		public IEnumerable<IExportStrategy> GetExportStrategies()
		{
			List<IExportStrategy> returnValue = new List<IExportStrategy>();
			IExportStrategy strategy = null;
			List<object> attributes = null;

			foreach (Type exportedType in assembly.ExportedTypes)
			{
				if (exportedType.GetTypeInfo().IsAbstract ||
					 exportedType.GetTypeInfo().IsInterface ||
					 excluding.Contains(exportedType))
				{
					continue;
				}

				try
				{
					attributes =
						new List<object>(exportedType.GetTypeInfo().GetCustomAttributes(true));

					if (exportAttributes)
					{
						strategy = ProcessAttributesOnType(exportedType, attributes);
					}

					if (strategy == null && interfaceTypes.Count > 0)
					{
						strategy = ProcessInterfacesTypes(exportedType);
					}

					if (strategy == null && basedOnTypes.Count > 0)
					{
						strategy = ProcessBaseTypes(exportedType);
					}

					if (strategy != null)
					{
						returnValue.Add(strategy);

						strategy = null;
					}
				}
				catch (Exception exp)
				{
					Logger.Error("Exception thrown while processing type: " + exportedType.FullName, supplemental, exp);
				}

				if (!registeredTypes.Contains(exportedType))
				{
					registeredTypes.Add(exportedType);

					try
					{
						MethodInfo registerMethod = null;

						foreach (MethodInfo declaredMethod in exportedType.GetTypeInfo().GetDeclaredMethods("RegisterExport"))
						{
							if (declaredMethod.IsStatic)
							{
								ParameterInfo[] parameters = declaredMethod.GetParameters();

								if (parameters.Length == 1 &&
									 parameters[0].ParameterType == typeof(IDependencyInjectionContainer))
								{
									registerMethod = declaredMethod;
									break;
								}
							}
						}

						if (registerMethod != null)
						{
							registerMethod.Invoke(null, new object[] { Container });
						}
					}
					catch (Exception exp)
					{
						Logger.Error("Exception thrown while calling RegisterExport for type: " + exportedType.FullName, supplemental, exp);
					}

					bool exportedAlready = strategy != null;

					foreach (IExportStrategyFactory exportStrategyFactory in strategyFactories)
					{
						try
						{
							strategy =
								exportStrategyFactory.CreateStrategy(Container, exportedType, attributes, exportedAlready);

							if (strategy != null)
							{
								returnValue.Add(strategy);
								exportedAlready = true;
							}
						}
						catch (Exception exp)
						{
							Logger.Error("Exception thrown while calling export factory", supplemental, exp);
						}
					}
				}
			}

			return returnValue;
		}

		public IExportAssemblyConfiguration When(ExportConditionDelegate conditionDelegate)
		{
			globalConditions.Add(new SimpleExportCondition(conditionDelegate));

			return this;
		}

		public IExportAssemblyConfiguration Unless(ExportConditionDelegate conditionDelegate)
		{
			globalConditions.Add(new SimpleExportCondition((x, y, z) => !conditionDelegate(x, y, z)));

			return this;
		}

		public IExportAssemblyConfiguration AndCondition(IExportCondition condition)
		{
			globalConditions.Add(condition);

			return this;
		}

		private IExportStrategy ProcessAttributesOnType(Type newExportType, IEnumerable<object> attributes)
		{
			int priority = 0;
			ExportEnvironment env = ExportEnvironment.Any;
			IExportStrategy newStrategy = null;
			bool suspendable = false;
			bool genericType = false;
			bool exporting = false;
			bool coreExport = false;
			int startOrder = (int)CoreExportStartOrder.Normal;
			TypeInfo tType = newExportType.GetTypeInfo();
			List<string> exportNames = new List<string>();
			List<Type> exportedTypes = new List<Type>();
			ILifeCycleContainerProviderAttribute lifeCycleContainerProviderAttribute = null;
			List<IExportCondition> conditions = new List<IExportCondition>(globalConditions);
			List<Tuple<EnrichWithDelegate, int>> enrichWithDelegates = new List<Tuple<EnrichWithDelegate, int>>();
			Dictionary<string, object> metadata = new Dictionary<string, object>();

			foreach (var customAttr in attributes)
			{
				IExportAttribute exportAttribute = customAttr as IExportAttribute;

				if (exportAttribute != null && !genericType)
				{
					exporting = true;

					if (exportAttribute.ExportTypes != null)
					{
						foreach (var exportedType in exportAttribute.ExportTypes)
						{
							if (exportedType.GetTypeInfo().IsGenericTypeDefinition)
							{
								exportNames.Clear();
								exportNames.Add(exportedType.FullName);
								exportedTypes.Add(exportedType);
								genericType = true;
								break;
							}

							exportNames.Add(exportedType.FullName);
							exportedTypes.Add(exportedType);
						}
					}
					else if (exportAttribute.ExportNames != null)
					{
						exportNames.AddRange(exportAttribute.ExportNames);
					}
					else
					{
						exportNames.Add(newExportType.Name);
					}
				}
				else if (customAttr is IExportStrategyProviderAttribute)
				{
					newStrategy = ((IExportStrategyProviderAttribute)customAttr).ProvideStrategy(newExportType, attributes);
				}
				else if (customAttr is ILifeCycleContainerProviderAttribute)
				{
					lifeCycleContainerProviderAttribute = customAttr as ILifeCycleContainerProviderAttribute;
				}
				else if (customAttr is IExportEnvironmentAttribute)
				{
					env = ((IExportEnvironmentAttribute)customAttr).Environment;
				}
				else if (customAttr is IExportPriorityAttribute)
				{
					priority = ((IExportPriorityAttribute)customAttr).Priority;
				}
				else if (customAttr is IExportConditionAttribute)
				{
					conditions.Add(((IExportConditionAttribute)customAttr).ProvideCondition(newExportType));
				}
				else if (customAttr is DataContractAttribute)
				{
					if (!tType.IsGenericTypeDefinition &&
						 !ReflectionService.KnownTypes.Contains(newExportType))
					{
						ReflectionService.AddToKnownTypes(newExportType);
					}
				}
				else if (customAttr is ICoreExportAttribute)
				{
					coreExport = true;

					startOrder = ((ICoreExportAttribute)customAttr).StartOrder;
				}
				else if (customAttr is IExportByInterfacesAttribute)
				{
					bool skipMicrosoftTypes = ((IExportByInterfacesAttribute)customAttr).SkipMicrosoftInterfaces;

					foreach (Type implementedInterface in
						newExportType.GetTypeInfo().ImplementedInterfaces)
					{
						if (!skipMicrosoftTypes ||
							 (!implementedInterface.FullName.StartsWith("Microsoft.") &&
							  !implementedInterface.FullName.StartsWith("System.")))
						{
							exporting = true;

							if (implementedInterface.GetTypeInfo().IsGenericTypeDefinition)
							{
								exportNames.Clear();
								exportNames.Add(implementedInterface.FullName);
								exportedTypes.Add(implementedInterface);
								genericType = true;
								break;
							}

							exportNames.Add(implementedInterface.FullName);
							exportedTypes.Add(implementedInterface);
						}
					}
				}
				else if (customAttr is IEnrichWithAttribute)
				{
					IEnrichWithAttribute enrichAttr = (IEnrichWithAttribute)customAttr;
					int enrichPriority;

					EnrichWithDelegate newEnrichWithDelegate =
						enrichAttr.ProvideDelegate(newExportType, out enrichPriority);

					enrichWithDelegates.Add(new Tuple<EnrichWithDelegate, int>(newEnrichWithDelegate, enrichPriority));
				}
				else if (customAttr is IExportMetadataAttribute)
				{
					KeyValuePair<string, object> newMetadata = ((IExportMetadataAttribute)customAttr).ProvideMetadata(newExportType);

					metadata.Add(newMetadata.Key, newMetadata.Value);
				}
			}

			if (exporting && newStrategy == null)
			{
				if (!genericType && !tType.IsGenericTypeDefinition)
				{
					newStrategy = new AttributedExportStrategy(exportNames, newExportType);

					newStrategy.ExportedTypes = exportedTypes;
				}
				else if (exportNames.Count > 0 && exportedTypes.Count > 0)
				{
					newStrategy =
						new GenericExportStrategy(
							exportNames, newExportType) { DiscoverAttributes = true };
				}
			}

			if (newStrategy != null)
			{
				newStrategy.Environment = env;
				newStrategy.Priority = priority;
				newStrategy.Suspendable = suspendable;
				newStrategy.CoreExportStartOrder = startOrder;
				newStrategy.IsCoreExport = coreExport;

				if (metadata.Count > 0)
				{
					newStrategy.Metadata = metadata;
				}
				
				if (enrichWithDelegates.Count > 0)
				{
					newStrategy.EnrichWithDelegates =
						enrichWithDelegates.OrderByDescending(x => x.Item2).Select(x => x.Item1).ToArray();
				}

				foreach (IExportCondition exportCondition in conditions)
				{
					newStrategy.AddCondition(exportCondition);
				}

				if (lifeCycleContainerProviderAttribute != null)
				{
					newStrategy.LifeCycleContainer = lifeCycleContainerProviderAttribute.ProvideContainer();
				}

				Logger.Debug(string.Format("Discovered type {0}", newExportType.FullName), supplemental);
			}

			return newStrategy;
		}

		private IExportStrategy ProcessInterfacesTypes(Type newExportType)
		{
			IExportStrategy returnValue = null;
			List<Type> exportTypes = new List<Type>();
			Type genericExport = null;

			foreach (Type implementedInterface in newExportType.GetTypeInfo().ImplementedInterfaces)
			{
				if (interfaceTypes.Contains(implementedInterface))
				{
					if (implementedInterface.GetTypeInfo().IsGenericTypeDefinition)
					{
						genericExport = implementedInterface;
						break;
					}

					exportTypes.Add(implementedInterface);
				}
			}

			if (genericExport != null)
			{
				returnValue = new GenericExportStrategy(
					new List<string> { genericExport.FullName }, newExportType) { DiscoverAttributes = true };
			}
			else if (exportTypes.Count > 0 && !newExportType.GetTypeInfo().IsGenericTypeDefinition)
			{
				List<string> exportNames = new List<string>();

				foreach (var exportName in exportTypes)
				{
					exportNames.Add(exportName.FullName);
				}

				returnValue = new AttributedExportStrategy(exportNames, newExportType);

				returnValue.ExportedTypes = exportTypes;
			}

			if (returnValue != null)
			{
				returnValue.Priority = assemblyPriority;
				returnValue.Environment = assemblyEnvironment;

				if (assemblySingleton)
				{
					returnValue.LifeCycleContainer = new PermanentlySharedLifeCycleContainer();
				}
				else if (assemblyShared)
				{
					returnValue.LifeCycleContainer = new WeakSharedLifeCycleContainer();
				}

				if (coreExportStart.HasValue)
				{
					returnValue.IsCoreExport = true;
					returnValue.CoreExportStartOrder = (int)coreExportStart.Value;
				}
			}

			return returnValue;
		}

		private IExportStrategy ProcessBaseTypes(Type newExportType)
		{
			IExportStrategy returnValue = null;

			if (!newExportType.GetTypeInfo().IsGenericTypeDefinition)
			{
				Type tempType = newExportType.GetTypeInfo().BaseType;

				while (tempType != null)
				{
					if (basedOnTypes.Contains(tempType))
					{
						returnValue = new AttributedExportStrategy(new List<string>
							                                           {
								                                           newExportType.Name
							                                           },
																				 newExportType);

						break;
					}

					tempType = tempType.GetTypeInfo().BaseType;
				}
			}

			if (returnValue != null)
			{
				returnValue.Priority = assemblyPriority;
				returnValue.Environment = assemblyEnvironment;

				if (assemblyShared)
				{
					if (assemblySingleton)
					{
						returnValue.LifeCycleContainer = new PermanentlySharedLifeCycleContainer();
					}
					else
					{
						returnValue.LifeCycleContainer = new WeakSharedLifeCycleContainer();
					}
				}

				if (coreExportStart.HasValue)
				{
					returnValue.IsCoreExport = true;
					returnValue.CoreExportStartOrder = (int)coreExportStart.Value;
				}
			}

			return returnValue;
		}
	}
}